package cn.xiongxf.service.impl;

import cn.xiongxf.constant.MQConstant;
import cn.xiongxf.constant.RedisKeyConstant;
import cn.xiongxf.context.LoginContextHolder;
import cn.xiongxf.doc.CourseDoc;
import cn.xiongxf.domain.*;
import cn.xiongxf.dto.CourseAddDto;
import cn.xiongxf.dto.MessageStationDto;
import cn.xiongxf.exception.GlobalCustomException;
import cn.xiongxf.feign.ESFeignClient;
import cn.xiongxf.feign.MediaFeignClient;
import cn.xiongxf.mapper.CourseMapper;
import cn.xiongxf.result.JsonResult;
import cn.xiongxf.service.*;
import cn.xiongxf.util.AssertUtil;
import cn.xiongxf.util.StrUtils;
import cn.xiongxf.vo.CourseDataForDetailVO;
import cn.xiongxf.vo.CourseOrderDataItemVO;
import cn.xiongxf.vo.CourseOrderDataVO;
import cn.xiongxf.vo.CourseStatusVO;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.mapper.EntityWrapper;
import com.baomidou.mybatisplus.mapper.Wrapper;
import com.baomidou.mybatisplus.service.impl.ServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.apache.rocketmq.client.producer.SendResult;
import org.apache.rocketmq.spring.core.RocketMQTemplate;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.messaging.Message;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.util.*;
import java.util.stream.Collectors;

/**
 * <p>
 *  服务实现类
 * </p>
 *
 * @author xiongxiaofu
 * @since 2022-08-16
 */
@Service
@Slf4j
@Configuration      // 用于标记配置类，兼备Component的效果
@EnableScheduling   // 开启定时任务
public class CourseServiceImpl extends ServiceImpl<CourseMapper, Course> implements ICourseService {
    @Autowired
    private ICourseDetailService courseDetailService;
    @Autowired
    private ICourseMarketService courseMarketService;
    @Autowired
    private ICourseResourceService courseResourceService;
    @Autowired
    private ICourseSummaryService courseSummaryService;
    @Autowired
    private ICourseTeacherService courseTeacherService;
    @Autowired
    private ICourseChapterService courseChapterService;
    @Autowired
    private ICourseUserLearnService courseUserLearnService;
    @Autowired
    private ITeacherService teacherService;
    @Autowired
    private ESFeignClient esFeignClient;
    @Autowired
    private RocketMQTemplate mqTemplate;
    @Autowired
    private RedisTemplate<Object,Object> redisTemplate;
    @Autowired
    private MediaFeignClient mediaFeignClient;

    /**
     * 保存课程
     */
    @Override
    @Transactional
    public void save(CourseAddDto dto) {
        Course course = dto.getCourse();
        CourseDetail courseDetail = dto.getCourseDetail();
        CourseMarket courseMarket = dto.getCourseMarket();
        CourseResource courseResource = dto.getCourseResource();
        List<Long> teacherIds = dto.getTeacherIds();

        // 1.参数校验

        // 2.条件校验：课程名不能重复
        String name = course.getName();
        Wrapper<Course> query = new EntityWrapper<>();
        query.eq("name",name);
        Course courseDB = selectOne(query);
        AssertUtil.isNull(courseDB,"课程名已存在！");

        // 3.保存课程表
        Login login = LoginContextHolder.getLogin();
        course.setLoginId(login.getId());
        course.setLoginUserName(login.getUsername());
        course.setGradeId(1L); // TODO
        course.setStatus(Course.STATUS_OFFLINE);
        course.setCourseTypeId(course.getCourseTypeId());
        // 多个老师格式处理
//        List<Teacher> teachers = teacherService.selectByIds(teacherIds);
        List<Teacher> teachers = teacherService.selectBatchIds(teacherIds);
        // List<Teacher> -> List<String> -> "陈老师，赵老师"
        String teacherNames = teachers.stream().map(teacher -> teacher.getName()).collect(Collectors.joining(","));
        course.setTeacherNames(teacherNames);
        insert(course);

        // 4.保存课程详情表
        Long courseId = course.getId();
        courseDetail.setId(courseId);
        courseDetailService.insert(courseDetail);

        // 5.保存课程营销表
        courseMarket.setId(courseId);
        courseMarketService.insert(courseMarket);

        // 6.保存课程资源表
        courseResource.setCourseId(courseId);
        courseResourceService.insert(courseResource);

        // 7.初始化课程数据汇总表
        CourseSummary courseSummary = new CourseSummary();
        courseSummary.setId(courseId);
        courseSummaryService.insert(courseSummary);

        // 8.保存课程_老师中间表
        /*for (Long teacherId : teacherIds) {
            CourseTeacher courseTeacher = new CourseTeacher();
            courseTeacher.setCourseId(courseId);
            courseTeacher.setTeacherId(teacherId);
            courseTeacherService.insert(courseTeacher);
        }*/
        teacherIds.forEach(teacherId -> {
            CourseTeacher courseTeacher = new CourseTeacher();
            courseTeacher.setCourseId(courseId);
            courseTeacher.setTeacherId(teacherId);
            courseTeacherService.insert(courseTeacher);
        });

    }

    /**
     * 课程上线
     */
    @Override
    public void onLineCourse(Long id) {
        // 1.参数校验
        AssertUtil.isNotNull(id, "非法请求！");
        Course course = selectById(id);
        AssertUtil.isNotNull(course, "非法请求！");

        // 2.条件校验：是否已经是上线状态
        if (course.getStatus().equals(Course.STATUS_ONLINE)) throw new GlobalCustomException("状态异常，该课程已经是上线状态!");

        // 3.上线：改状态，设置上线时间，更新DB
        course.setStatus(Course.STATUS_ONLINE);
        course.setOnlineTime(new Date());
        updateById(course);

        // 4.将课程相关信息同步到CourseDoc对象
        CourseDoc doc = getCourseDoc(course,id);

        // 5.调用feign接口，保存CourseDoc对象
        JsonResult jsonResult = esFeignClient.saveCourseDoc(doc);
        AssertUtil.isTrue(jsonResult.isSuccess(), jsonResult.getMessage());  // 如果保存失败，抛异常

        // 6.1.消息站内发布
        try {  // 此处使用try-catch结构捕捉到发布消息的异常，可避免updateById(course)回滚
            publishMessageStation(course);
        } catch (Exception e) {
            e.printStackTrace();
        }

        // 6.2.消息短信发送
        try {  // 此处使用try-catch结构捕捉到发布消息的异常，可避免updateById(course)回滚
            publishMessageSms(course);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    // 抽取方法：将课程相关信息同步到CourseDoc对象
    private CourseDoc getCourseDoc(Course course,Long id) {
        CourseDoc doc = new CourseDoc();
        CourseMarket courseMarket = courseMarketService.selectById(id);
        CourseSummary courseSummary = courseSummaryService.selectById(id);
        if (course != null) BeanUtils.copyProperties(course, doc);
        if (courseMarket != null) BeanUtils.copyProperties(courseMarket, doc);
        if (courseSummary != null) BeanUtils.copyProperties(courseSummary, doc);
        doc.setChargeName(courseMarket.getCharge().equals(1) ? "免费" : "收费");
        return doc;
    }

    // 抽取方法：消息站内发布
    private void publishMessageStation(Course course) {
        // 给VIP用户（批量）
        // 查询系统所有的VIP用户
        String content = "尊敬的VIP用户，您好！最新课程 %s 发布啦, <a href='http://course.skcc.com/detial.html?courseId=%s'>点击查看详情</a> ";
        content = String.format(content, course.getName(), course.getId());
        MessageStationDto messageStationDto = new MessageStationDto("课程发布",content,"课程消息", Arrays.asList(1L,2L,3L) );
        // 构建消息
        Message<String> message = MessageBuilder.withPayload(JSON.toJSONString(messageStationDto)).build();
        // 发送消息
        SendResult sendResult = mqTemplate.syncSend(MQConstant.TOPIC_MESSAGE_STATION + ":" + MQConstant.TAGS_MESSAGE_STATION, message);
        // 消息推送失败的处理方式：1.回滚   2.重试   3.记录到数据库   4.不管
        log.info("站内消息发布：{}", sendResult);
    }

    // 抽取方法：消息短信发送
    private void publishMessageSms(Course course) {
        // 给VIP用户（批量）
        // 查询系统所有的VIP用户
        String content = "尊敬的VIP用户，您好！最新课程 %s 发布啦, <a href='http://course.skcc.com/detial.html?courseId=%s'>点击查看详情</a> ";
        content = String.format(content, course.getName(), course.getId());
        MessageStationDto messageStationDto = new MessageStationDto("课程发布",content,"课程消息", Arrays.asList(1L,2L,3L) );
        // 构建消息
        Message<String> message = MessageBuilder.withPayload(JSON.toJSONString(messageStationDto)).build();
        // 发送消息
        SendResult sendResult = mqTemplate.syncSend(MQConstant.TOPIC_MESSAGE_SMS + ":" + MQConstant.TAGS_MESSAGE_SMS, message);
        // 消息推送失败的处理方式：1.回滚   2.重试   3.记录到数据库   4.不管
        log.info("消息短信发送：{}", sendResult);
    }

    /**
     * 课程下线
     */
    @Override
    public void offLineCourse(Long id) {
        // 1.参数校验
        AssertUtil.isNotNull(id, "非法请求！");
        Course course = selectById(id);
        AssertUtil.isNotNull(course, "非法请求！");

        // 2.条件校验：是否已经是下线状态
        if (course.getStatus().equals(Course.STATUS_OFFLINE)) throw new GlobalCustomException("状态异常，该课程已经是下线状态!");

        // 3.下线：改状态，更新DB
        course.setStatus(Course.STATUS_OFFLINE);
        updateById(course);

        // 4.调用feign接口，删除CourseDoc对象
        JsonResult jsonResult = esFeignClient.deleteCourseDoc(id);
        AssertUtil.isTrue(jsonResult.isSuccess(), jsonResult.getMessage());  // 如果删除失败，抛异常
    }

    /**
     * 课程加入推荐
     */
    @Override
    public void recommendCourse(Long id) {
        // 1.参数校验
        AssertUtil.isNotNull(id, "非法请求！");
        Course course = selectById(id);
        AssertUtil.isNotNull(course, "非法请求！");

        // 2.条件校验：是否已经是推荐状态
        if (null != course.getRecommend() && course.getRecommend().equals(Course.RECOMMEND_YES)) throw new GlobalCustomException("状态异常，该课程已经是推荐状态!");

        // 3.加入推荐：改状态，更新DB
        course.setRecommend(Course.RECOMMEND_YES);
        updateById(course);

        // 删除Redis中缓存的推荐课程
        redisTemplate.delete(RedisKeyConstant.RECOMMEND_COURSES_KEY);

//        // 4.将课程相关信息同步到CourseDoc对象
//        CourseDoc doc = getCourseDoc(course,id);
//
//        // 5.调用feign接口，保存CourseDoc对象
//        JsonResult jsonResult = esFeignClient.saveCourseDoc(doc);
//        AssertUtil.isTrue(jsonResult.isSuccess(), jsonResult.getMessage());  // 如果保存失败，抛异常
        // 只是推荐这一个字段不一样，无需保存到ES
    }

    /**
     * 获取推荐课程
     */
    @Override
    public List<Course> getRecommendCourses() {
        List<Course> recommendCourses = (List<Course>) redisTemplate.opsForValue().get(RedisKeyConstant.RECOMMEND_COURSES_KEY);
        if (recommendCourses == null){
            log.info("从MySQL中获取推荐课程并存入Redis=============================================");
            Wrapper<Course> query = new EntityWrapper<>();
            query.eq("recommend", Course.RECOMMEND_YES);
            recommendCourses = selectList(query);
            redisTemplate.opsForValue().set(RedisKeyConstant.RECOMMEND_COURSES_KEY, recommendCourses);
        }
        return recommendCourses;
    }

    /**
     * 获取热门推荐课程
     */
    @Override
    public List<Course> getHotRecommendClourses() {
        List<Course> hotRecommendClourses = (List<Course>) redisTemplate.opsForValue().get(RedisKeyConstant.HOT_RECOMMEND_COURSES_KEY);
        if (hotRecommendClourses == null){
            // 按评论对CourseSummary排序
            Wrapper<CourseSummary> query = new EntityWrapper<>();
            query.orderBy("comment_count",false);

            // 根据相同的id找到课程，添加到热门推荐课程中
            List<CourseSummary> hotCourseSummaries = courseSummaryService.selectList(query);
            hotRecommendClourses = new ArrayList<>();
            for (CourseSummary hotCourseSummary : hotCourseSummaries) {
                Long id = hotCourseSummary.getId();
                Course course = selectById(id);
                hotRecommendClourses.add(course);
            }
            redisTemplate.opsForValue().set(RedisKeyConstant.HOT_RECOMMEND_COURSES_KEY,hotRecommendClourses);
        }
        return hotRecommendClourses;
    }

    // 执行定时任务，每隔一小时，就删除Redis中缓存的热门推荐课程
    @Scheduled(fixedRate = 60*60*1000)
    public void deleteRedisHotRecommendClourses(){
        redisTemplate.delete(RedisKeyConstant.HOT_RECOMMEND_COURSES_KEY);
        log.info("执行静态定时任务的时间：{}  =================================", new Date());
    }


    /**
     * 根据课程ID获取课课数据给详情页
     */
    @Override
    public CourseDataForDetailVO selectCourseDataForDetail(Long courseId) {
        // 参数校验
        AssertUtil.isNotNull(courseId,"非法请求!");
        // 查询课程
        Course course = selectById(courseId);
        AssertUtil.isNotNull(course,"非法请求！该课程不存在！");
        // 条件校验：课程必须上线
        if (course.getStatus().equals(Course.STATUS_OFFLINE)) throw new GlobalCustomException("该课程未上线！");
        // 查询课程营销
        CourseMarket courseMarket = courseMarketService.selectById(courseId);
        // 查询课程详情
        CourseDetail courseDetail = courseDetailService.selectById(courseId);
        // 查询课程汇总
        CourseSummary courseSummary = courseSummaryService.selectById(courseId);
        // 查询老师列表
        List<Teacher> teachers = selectTeachersByCourseId(courseId);
        // 查询章节列表
        List<CourseChapter> courseChapters = selectChaptersByCourseId(courseId);
        // 返回VO对象
        return new CourseDataForDetailVO(course,courseMarket,courseSummary,courseDetail,teachers,courseChapters);
    }

    // 抽取方法：根据课程ID查询老师列表
    private List<Teacher> selectTeachersByCourseId(Long courseId){
        Wrapper<CourseTeacher> wrapper = new EntityWrapper<>();
        wrapper.eq("course_id" ,courseId);
        // 查询课程_老师的中间对象
        List<CourseTeacher> courseTeachers = courseTeacherService.selectList(wrapper);
        // 查询所有老师的ID
        List<Long> teacherIds = courseTeachers
                .stream()
                .map(CourseTeacher::getTeacherId)
                .collect(Collectors.toList());
        // 查询老师列表
        return teacherService.selectBatchIds(teacherIds);
    }

    // 抽取方法：根据课程ID查询章节
    private List<CourseChapter> selectChaptersByCourseId(Long courseId){
        // 查询所有章节
        Wrapper<CourseChapter> wrapper = new EntityWrapper<>();
        wrapper.eq("course_id",courseId);
        List<CourseChapter> courseChapters = courseChapterService.selectList(wrapper);

        // 查询章节下的视频
        // 1.media服务编写controller查询视频
        // 2.编写api-media模块
        // 3.课程整合api-media模块调用Feign接口，查询视频列表
        JsonResult jsonResult = mediaFeignClient.selectByCourseId(courseId);
        // 查询不成功，抛异常
        AssertUtil.isTrue(jsonResult.isSuccess(),jsonResult.getMessage());
        // 未查到数据，给出提示
        AssertUtil.isNotNull(jsonResult.getData(),"视频列表加载失败！");
        // 视频列表 MediaFile -> byte[] -> LinkedHashMap
        String jsonStr = JSON.toJSONString(jsonResult.getData());  // 反序列化
        List<MediaFile> mediaFiles = JSON.parseArray(jsonStr, MediaFile.class);

        // 把章节List转为[e.id : e]Map，方便查询
        Map<Long, CourseChapter> courseChapterMap = courseChapters
                .stream()
                .collect(Collectors.toMap(CourseChapter::getId, CourseChapter -> CourseChapter));

        // 4.把视频列表装进对应的章节
        mediaFiles.forEach(mediaFile -> {
            // 播放地址置空，防止通过F12获取m3u8视频地址
            mediaFile.setFileUrl("");
            CourseChapter courseChapter = courseChapterMap.get(mediaFile.getChapterId());
            if(courseChapter != null) courseChapter.getMediaFiles().add(mediaFile);
        });

        return courseChapters;
    }


    /**
     * 根据课程ID查询课程状态给用户
     */
    @Override
    public CourseStatusVO selectCourseStatusForUser(Long courseId) {
        // 根据课程ID查询课程的状态： 是否免费，是否购买，是否上线
        AssertUtil.isNotNull(courseId,"非法请求！课程ID不能为空！");
        Course course = selectById(courseId);
        AssertUtil.isNotNull(course,"非法请求！课程不能为空！");
        // 查询课程营销
        CourseMarket courseMarket = courseMarketService.selectById(courseId);
        AssertUtil.isNotNull(courseMarket,"课程不存在!");

        // 登录对象
        Long loginId = 9L;  // todo，假数据，等登录做了再改

        // 判断是否免费
        boolean isFree = (courseMarket.getCharge() != null && courseMarket.getCharge().equals(CourseMarket.CHARGE_FREE));
        // 判断是否上线
        boolean isOnline = course.getStatus().equals(Course.STATUS_ONLINE);
        // 判断是否购买
        boolean isbought = (null != selectByUserIdAndCourseId(loginId,courseId));

        // 封装到VO对象
        CourseStatusVO courseStatusVO = new CourseStatusVO();
        courseStatusVO.setFree(isFree);
        courseStatusVO.setOnline(isOnline);
        courseStatusVO.setBought(isbought);

        return courseStatusVO;
    }

    // 抽取方法：查询用户是否已购买课程
    private CourseUserLearn selectByUserIdAndCourseId(Long loginId, Long courseId){
        Wrapper<CourseUserLearn> wrapper = new EntityWrapper<>();
        wrapper.eq("login_id" , loginId);
        wrapper.eq("course_id" , courseId);
        wrapper.eq("state",0);  // 查询有效的
        return courseUserLearnService.selectOne(wrapper);
    }


    /**
     * 根据课程ID查询课程数据给订单页
     */
    @Override
    public CourseOrderDataVO selectCourseDataForOrder(String courseIdsStr) {
        // 参数校验
        AssertUtil.isNotNull(courseIdsStr,"非法请求！");
        List<Long> courseIds = StrUtils.splitStr2LongArr(courseIdsStr);
        List<Course> courses = selectBatchIds(courseIds);
        AssertUtil.isNotEmpty(courses, "非法请求！");
        AssertUtil.isEquals(courseIds.size(),courses.size(), "非法请求！");

        // 条件校验
        // 批量查询营销
        List<CourseMarket> courseMarkets = courseMarketService.selectBatchIds(courseIds);
        // 将营销List转为entry[e.id:e]Map结构
        Map<Long, CourseMarket> courseMarketMap = courseMarkets.stream().collect(Collectors.toMap(CourseMarket::getId, CourseMarket -> CourseMarket));
        // 课程订单数据子VO列表
        ArrayList<CourseOrderDataItemVO> courseOrderDataItemVOs = new ArrayList<>(courseIds.size());
        // 初始总价
        BigDecimal totalAmount = new BigDecimal("0");

        for (Course course : courses) {  // 此处不用forEach()：因为foreach()无法赋值循环体外的变量
            CourseMarket courseMarket = courseMarketService.selectById(course.getId());
            // 防止通过手动改地址栏获取到未上架的订单
            AssertUtil.isEquals(course.getStatus(), Course.STATUS_ONLINE, "非法请求！该课程未上线！");
            AssertUtil.isEquals(courseMarket.getCharge(), CourseMarket.CHARGE_NOT_FREE, "非法请求！有免费课程，请重新下单！");
            // 将课程和课程营销封装成子VO，添加到子VO列表中
            courseOrderDataItemVOs.add(new CourseOrderDataItemVO(course, courseMarketMap.get(course.getId())));
            //  计算总价
            totalAmount = totalAmount.add(courseMarket.getPrice());
        }

        // 返回封装后的父VO
        return new CourseOrderDataVO(courseOrderDataItemVOs, totalAmount);
    }



}
